"""
内容：搭建多层感知神经网络模型
日期：2020年6月30日
作者：Howie
"""

# 调用要使用的包
from sklearn.model_selection import train_test_split
import pandas as pd
import os
from keras.models import Sequential
from keras.layers import Dense
from keras.utils import plot_model
import matplotlib.pyplot as plt
from keras.callbacks import TensorBoard, EarlyStopping

# 预设
N_CLASSES = 1
N_FEATURES = 8
RANDOM_SEED = 0
DATASET_PATH = '../dataset/pima-indians-diabetes/Pima.csv'
HIST_PATH = './logs/Demo1_Acc-Loss Curve.pdf'
MODEL_PATH = './infer_model/'  # 模型保存路径
GRAPH_SAVE_PATH = './graph/'  # TensorBoard可视化
tb_hist = TensorBoard(
    log_dir=GRAPH_SAVE_PATH,
    histogram_freq=0,
    write_graph=True,
    write_images=True)
early_stopping = EarlyStopping(  # 设置早停，监控每个训练周期的验证精度
    monitor='val_accuracy',
    patience=20
)
CALL_BACK_FUNC = [tb_hist, early_stopping]  # 回调函数
# 超参
HIDDEN_LAYER_UNITS = [12, 8]
EPOCHS = 1500
BATCH_SIZE = 64
TEST_SPLIT_RATE = 0.3
VAL_SPLIT_RATE = 0.2


def hist_plot(hist):
    """
    # 可视化训练过程
    :param hist: history对象
    :return:
    """
    fig, loss_ax = plt.subplots()
    acc_ax = loss_ax.twinx()
    # 每个训练周期的训练误差与验证误差
    loss_ax.plot(hist.history['loss'], 'y', label='train loss')
    loss_ax.plot(hist.history['val_loss'], 'r', label='val loss')
    # 每个训练周期的训练精度与验证精度
    acc_ax.plot(hist.history['accuracy'], 'b', label='train acc')
    acc_ax.plot(hist.history['val_accuracy'], 'g', label='val acc')
    # 横轴与纵轴
    loss_ax.set_xlabel('epoch')
    loss_ax.set_ylabel('loss')
    acc_ax.set_ylabel('accuracy')
    # 标签
    loss_ax.legend(loc='upper left')
    acc_ax.legend(loc='lower left')
    # 保存
    plt.savefig(HIST_PATH)
    # 展示
    plt.show()


def load_dataset(dataset_path):
    """
    # 生成数据集
    :param dataset_path: 数据集路径
    :return: 划分好的训练集和测试集
    """
    data = pd.read_csv(dataset_path, delimiter=',')     # 准备数据
    samples = data.drop('Outcome', axis=1).values   # 样本
    labels = data['Outcome']    # 标签
    # 生成数据集
    X_train, X_test, Y_train, Y_test = train_test_split(
        samples, labels, test_size=TEST_SPLIT_RATE, random_state=RANDOM_SEED)
    print('train samples: {}\n'
          'test samples: {}'.format(X_train.shape[0], X_test.shape[0]))
    return X_train, X_test, Y_train, Y_test


def model_building():
    """
    # 搭建模型
    :return: 搭建好的模型
    """
    model = Sequential()
    model.add(
        Dense(
            HIDDEN_LAYER_UNITS[0],
            input_dim=N_FEATURES,
            activation='relu'))
    model.add(Dense(HIDDEN_LAYER_UNITS[1], activation='relu'))
    model.add(Dense(N_CLASSES, activation='sigmoid'))
    plot_model(model=model, to_file='./logs/Demo2_model.pdf', show_shapes=True)
    return model


def model_training():
    """
    # 训练模型
    :return:
    """
    X_train, X_test, Y_train, Y_test = load_dataset(DATASET_PATH)
    model = model_building()
    # 设置模型训练过程
    model.compile(
        loss='binary_crossentropy',
        optimizer='adam',
        metrics=['accuracy'])
    # 训练模型
    hist = model.fit(
        X_train,
        Y_train,
        epochs=EPOCHS,
        batch_size=BATCH_SIZE,
        verbose=True,
        validation_split=0.3,
        callbacks=CALL_BACK_FUNC)
    # 保存已训练模型
    model.save(os.path.join(MODEL_PATH, 'diabetes_mlp_model.h5'))
    # 评价模型
    hist_plot(hist)  # 可视化训练过程
    scores = model.evaluate(X_test, Y_test)
    print("%s on test set: %.2f%%" % (model.metrics_names[1], scores[1] * 100))


if __name__ == '__main__':
    model_training()
